package org.spiderflow.core.script;

import jdk.nashorn.api.scripting.ScriptObjectMirror;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spiderflow.core.expression.ExpressionTemplate;
import org.spiderflow.core.expression.ExpressionTemplateContext;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ScriptManager {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScriptManager.class);

    private static ScriptEngine scriptEngine;

    private static final Set<String> FUNCTIONS = new HashSet<>();

    private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();

    private ScriptManager() {
    }

    public static void setScriptEngine(ScriptEngine engine) {
        ScriptManager.scriptEngine = engine;
        String format = "var ExpressionTemplate = Java.type('%s');" +
                "var ExpressionTemplateContext = Java.type('%s');" +
                "function _eval(expression) {" +
                "return ExpressionTemplate.create(expression).render(ExpressionTemplateContext.get());" +
                "}";
        try {
            String s = String.format(format, ExpressionTemplate.class.getName(), ExpressionTemplateContext.class.getName());
            ScriptManager.scriptEngine.eval(s);
        } catch (ScriptException e) {
            LOGGER.error("注册_eval函数失败", e);
        }
    }

    public static void clearFunctions() {
        FUNCTIONS.clear();
    }

    public static ScriptEngine createEngine() {
        return new ScriptEngineManager().getEngineByName("nashorn");
    }

    public static void lock() {
        LOCK.writeLock().lock();
    }

    public static void unlock() {
        LOCK.writeLock().unlock();
    }

    public static void registerFunction(ScriptEngine engine, String functionName, String parameters, String script) {
        try {
            engine.eval(concatScript(functionName, parameters, script));
            FUNCTIONS.add(functionName);
            LOGGER.info("注册自定义函数{}成功", functionName);
        } catch (ScriptException e) {
            LOGGER.warn("注册自定义函数{}失败", functionName, e);
        }
    }

    private static String concatScript(String functionName, String parameters, String script) {
        String format = "function %s(%s){%s}";
        String params = parameters == null ? "" : parameters;
        return String.format(format, functionName, params, script);
    }

    public static boolean containsFunction(String functionName) {
        try {
            LOCK.readLock().lock();
            return FUNCTIONS.contains(functionName);
        } finally {
            LOCK.readLock().unlock();
        }
    }

    public static void validScript(String functionName, String parameters, String script) throws ScriptException {
        new ScriptEngineManager().getEngineByName("nashorn")
                                 .eval(concatScript(functionName, parameters, script));
    }

    public static Object eval(ExpressionTemplateContext context, String functionName, Object... args) throws ScriptException, NoSuchMethodException {
        if ("_eval".equals(functionName)) {
            if (args == null || args.length != 1) {
                throw new ScriptException("_eval必须要有一个参数");
            } else {
                return ExpressionTemplate.create(args[0].toString()).render(context);
            }
        }
        if (scriptEngine == null) {
            throw new NoSuchMethodException(functionName);
        }
        try {
            LOCK.readLock().lock();
            return convertObject(((Invocable) scriptEngine).invokeFunction(functionName, args));
        } finally {
            LOCK.readLock().unlock();
        }
    }

    private static Object convertObject(Object object) {
        if (object instanceof ScriptObjectMirror) {
            ScriptObjectMirror mirror = (ScriptObjectMirror) object;
            if (mirror.isArray()) {
                int size = mirror.size();
                Object[] array = new Object[size];
                for (int i = 0; i < size; i++) {
                    array[i] = convertObject(mirror.getSlot(i));
                }
                return array;
            } else {
                String className = mirror.getClassName();
                if ("Date".equalsIgnoreCase(className)) {
                    return new Date(mirror.to(Long.class));
                }
                //其它类型待处理
            }

        }
        return object;
    }
}
